import $Element from "../Element";
type TDraggedInfo = {
    dragging: boolean,
    sizing: boolean,
    initMouseX: number,
    initMouseY: number,
    dragEleLeft: number,
    dragEleTop: number
}
const draggedWeakMap = new WeakMap<HTMLElement, TDraggedInfo>();
function getMapTarget(ele: HTMLElement) {
    let draggedInfo = draggedWeakMap.get(ele);
    if (draggedInfo) {
        return draggedInfo;
    } else {
        draggedInfo = { dragging: false, sizing: false, initMouseX: 0, initMouseY: 0, dragEleLeft: 0, dragEleTop: 0 };
        draggedWeakMap.set(ele, draggedInfo);
        return draggedInfo;
    }

}
export enum EDrageDirection {
    X,
    Y,
    Both
}
interface IDragConf {
    autoReBackRange?: boolean;    //是否自动弹回有效区
    dragBothDirct?: EDrageDirection;
    leftBorder?: number;
    rightBorder?: number;
    topBorder?: number;
    bottomBorder?: number;
}
/**
 * 
 * @param container 拖拽容器
 * @param draggedEle 被拖拽的对象$Element
 * @param autoReBackRange 离开区域后是否自动弹回，默认为true
 */
export function DrageMove(container: $Element, draggedEle: $Element, autoReBackRange?: IDragConf) {
    autoReBackRange = autoReBackRange || {
        autoReBackRange: true,
        dragBothDirct: EDrageDirection.Both,
        leftBorder: 0,
        rightBorder: 0,
        topBorder: 0,
        bottomBorder: 0
    }
    let frontInterceptors: { [key: string]: Array<() => boolean | void> } = {};
    let endHooks: { [key: string]: Array<(evt:Event) => void> } = {};
    let currentMoveTarget: HTMLElement | null = null;
    container.addEventListener("mousedown", mouseDown);
    container.addEventListener("mousemove", mouseMove);
    container.addEventListener("mouseup", mouseUp);

    function getCss(ele: HTMLElement, prop: any) {
        return parseInt(window.getComputedStyle(ele)[prop]);
    }
    function mouseDown(evt: Event) {
        const evtTarget = <HTMLElement>evt.target;
        if (draggedEle.is(evtTarget)) {
            if (executInterceptors("mousedown") === false) {
                return false;
            }
            let draggedInfo = getMapTarget(evtTarget);
            draggedInfo.dragging = true;
            draggedInfo.initMouseX = (evt as MouseEvent).clientX;
            draggedInfo.initMouseY = (evt as MouseEvent).clientY;
            draggedInfo.dragEleLeft = getCss(evtTarget, "left");
            draggedInfo.dragEleTop = getCss(evtTarget, "top");
            currentMoveTarget = evtTarget;
            executHook("mousedown",evt)
        }
    }
    function mouseMove(evt: Event) {

        if (!currentMoveTarget) return;
        // const evtTarget = <HTMLElement> evt.target;
        let draggedInfo = getMapTarget(currentMoveTarget);

        if (draggedInfo.dragging === true) {
            if (executInterceptors("mousemove") === false) {
                return false;
            }
            const nowX = (<MouseEvent>evt).clientX,
                nowY = (<MouseEvent>evt).clientY,
                disX = nowX - draggedInfo.initMouseX,
                disY = nowY - draggedInfo.initMouseY,
                leftLocation = draggedInfo.dragEleLeft + disX,
                topLocation = draggedInfo.dragEleTop + disY;
            if (autoReBackRange?.dragBothDirct == EDrageDirection.X || autoReBackRange?.dragBothDirct == EDrageDirection.Both) {
                currentMoveTarget.style.left = leftLocation + "px";
            }
            if (autoReBackRange?.dragBothDirct == EDrageDirection.Y || autoReBackRange?.dragBothDirct == EDrageDirection.Both) {
                currentMoveTarget.style.top = topLocation + "px";
            }
            executHook("mousemove",evt)
        }
    }
    function mouseUp(evt: Event) {
        if (executInterceptors("mouseup") === false) {
            return false;
        }
        const evtTarget = <HTMLElement>evt.target;
        let draggedInfo = getMapTarget(evtTarget);
        draggedInfo.dragging = false;
        draggedInfo.dragEleLeft = getCss(evtTarget, "left");
        draggedInfo.dragEleTop = getCss(evtTarget, "top");
        currentMoveTarget = null;
        executHook("mouseup",evt)
    }

    function executInterceptors(evtName: string): boolean | void {
        let allInterceptors = frontInterceptors[evtName];
        try {
            if (allInterceptors) {
                for (let i = 0; i < allInterceptors.length; i++) {
                    if (allInterceptors[i]() === false) {
                        return false;
                    }
                }
            }
        } catch (ex) {
            // console.error(ex);
            console.error(`DragMoveFrontInterceptorError: EventName: '${evtName}', Error:${ex}`);
        }
    }

    function executHook(evtName: string,event:Event) {
        let hooks = endHooks[evtName];
        try {
            hooks && hooks.forEach(hook => hook(event));
        } catch (ex) {
            console.error(`DragMoveEndHookError: ${ex}`);
        }
    }
    return {
        removeDrag() {
            container.removeEventListener("mousedown");
            container.removeEventListener("mousemove");
            container.removeEventListener("mouseup");
        },
        /**
         * 前置拦截器会在事件之前发生，返回false，屏蔽原有事件，并阻止事件冒泡
         * 如果存在多个拦截器，则其中任何一个返回false，就会阻断事件执行
         */
        addEventFrontInterceptor(eventName: string, callBack: () => boolean | void) {
            if (frontInterceptors[eventName]) {
                frontInterceptors[eventName].push(callBack)
            } else {
                frontInterceptors[eventName] = [callBack];
            }
        },
        /**
         * 事件后置钩子，在事件末尾执行
         */
        addEventEndHook(eventName: string, callBack: (evt:Event) => void) {
            if (endHooks[eventName]) {
                endHooks[eventName].push(callBack)
            } else {
                endHooks[eventName] = [callBack];
            }
            return this;
        }
    }
}

